import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)

# -*- coding: utf8 -*-
from scapy.all import *

"""
Ejercicio 1.a
~~~~~~~~~~~~~

Se debe implementar ARPing y devolver una tupla (is_alive, response) en donde:
- is_alive de tipo bool indica si el host con con IP dst_ip esta vivo, y
- response es el paquete que se obtuvo como respuesta. Se define en None si is_alive == False.
"""
#def arping(dst_ip, src_ip):
def arping(dst_ip):
    is_alive = response = None

    # Trabajamos en capa 2
    # Armamos un paquete ARP que hace broadcast
    # Tiene tipo de operacion who-has por default
    a = ARP(pdst=dst_ip, hwdst="FF:FF:FF:FF:FF:FF", op=1)
    response = sr1(a, retry=3, timeout=50, verbose=1)
    # Si no nos contestan que esa IP pertenece a una MAC entonces suponemos que no esta vivo el host.
    if not (response == None):
        is_alive = not (response.op == 2)	
    else:
        is_alive = False

    if not is_alive:
        response = None

    return is_alive, response



"""
Ejercicio 1.b
~~~~~~~~~~~~~

Se debe implementar ICMPing y devolver una tupla (is_alive, response) en donde:
- is_alive de tipo bool indica si el host con IP dst_ip esta vivo, y 
- response es el paquete que se obtuvo como respuesta. Se define en None si is_alive == False.
"""
def icmping(dst_ip):
    is_alive = response = None
    # Trabajamos en capa 3
    # Armamos un paquete IP que usa ICMP
    # Por default ya es del tipo Echo Request
    p = IP(dst = dst_ip) / ICMP()    
    response = sr1(p, retry=3, timeout=50, verbose=1)

    if not (response == None):	
        # Responde echo reply
        is_alive = response.type == 0
    else:
        is_alive = False

    if not is_alive:
        response = None

    return (is_alive, response)

# ----------------------------------------------------------------------
# ----------------------------- Auxiliares -----------------------------
# ----------------------------------------------------------------------

# Transforma una ip de la forma num.num.num.num a una representacion binaria correspondiente
def ip_to_bin(ip):
    pos_punto_pre = 0
    pos_punto_pos = 0    
    ip_bin = ""

    for i in range(3):
        pos_punto_pos = ip.find(".", pos_punto_pre)
    sub_str = ip[pos_punto_pre:pos_punto_pos]      

    # Pasamos a binario
    sub_str = bin(int(sub_str))

    # Sacamos los dos primeros caracteres que son "0b"
    sub_str = sub_str[2:]

    # Completar con "0"s hasta llegar a la longitud de 8 bits 
    while(len(sub_str) < 8):
        sub_str = "0" + sub_str

    ip_bin += sub_str
    pos_punto_pre = pos_punto_pos + 1

    # Agregamos lo que falta

    sub_str = ip[pos_punto_pre:]

    # Pasamos a binario
    sub_str = bin(int(sub_str))

    # Sacamos los dos primeros caracteres que son "0b"
    sub_str = sub_str[2:]

    # Completar con "0"s hasta llegar a la longitud de 8 bits 
    while(len(sub_str) < 8):
        sub_str = "0" + sub_str

    ip_bin += sub_str

    return ip_bin


# Transforma una ip en binario (32 bits) a una representacion de la forma num.num.num.num correspondiente
def bin_to_ip(ip_bin):
    pos = 0    
    ip = ""

    for i in range(3):
        sub_str = ip_bin[pos:pos + 8]      

        # Pasamos a decimal
        sub_str = str(int(sub_str, 2))

        ip += sub_str + "."
        pos += 8

    # Agregamos el ultimo
    sub_str = ip_bin[pos:]      

    # Pasamos a decimal
    sub_str = str(int(sub_str, 2))

    ip += sub_str

    return ip


# Toma una direccion ip y la incrementa en 1
def inc(ip):
    pos_punto_pre = 0
    pos_punto_pos = 0    
    ip_vec = []

    for i in range(3):
        pos_punto_pos = ip.find(".", pos_punto_pre)
    sub_str = ip[pos_punto_pre:pos_punto_pos]      

    # Pasamos a formato numerico
    sub_str = int(sub_str)

    ip_vec.append(sub_str)

    pos_punto_pre = pos_punto_pos + 1

    # Agregamos lo que falta

    sub_str = ip[pos_punto_pre:]

    # Pasamos a formato numerico
    sub_str = int(sub_str)

    ip_vec.append(sub_str)

    # Sumamos uno
    ip_vec[3] += 1

    i = 3
    while(ip_vec[i] > 255):
        ip_vec[i] = 0
    ip_vec[i - 1] += 1
    i -= 1

    # Reconstruir la ip

    ip_res = ""

    for i in range(3):
        ip_res += str(ip_vec[i]) + "."
    ip_res += str(ip_vec[3])

    return ip_res

# Toma un rango de red escrito en formato CIDR y devuelve la lista de todas las ips pertenecientes a ese rango.
def make_range(rango_red):
    pos = rango_red.find("/")
    dir_subred = rango_red[0:pos]
    sufix = rango_red[pos+1:]

    # Pasamos a binario la direccion de subred
    dir_subred_num = ip_to_bin(dir_subred)

    unos = "11111111111111111111111111111111"
    sufix = int(sufix)
    mask = unos[:sufix]

    while(len(mask) < 32):
        mask += "0"

    # Para obtener la direccion base hacemos un and logico entre la mascara la direccion de subred, todo en binario
    dir_subred_base = ""
    for i in range(32):
        s = str(int(dir_subred_num[i]) & int(mask[i]))
    dir_subred_base += s

    # Ahora que tenemos la direccion base hay que ir agregando todas las ips a la lista
    # Hay que ir sumando de a uno en binario y despues pasar eso a ip
    # Hay 2^{32 - sufix} direcciones

    rango = []
    dir = bin_to_ip(dir_subred_base)
    cant = 2 ** (32 - sufix)

    for i in range(cant):
        rango.append(dir)
    dir = inc(dir)	

    return rango

"""
Ejercicio 2.a
~~~~~~~~~~~~~

Se debe devolver la lista discovered conteniendo tuplas (is_alive, ip) para cada direccion
IP buscada via ARP, en donde:
* is_alive de tipo bool indica si el host con IP dst_ip esta vivo, y 
* ip es dicha IP.
"""


def arp_discovery(rango_red):
    discovered = []    
    rango = make_range(rango_red)    

    for dst_ip in rango:
        t = arping(dst_ip)
        discovered.append((t[0], dst_ip))

    return discovered



"""
Ejercicio 2.b
~~~~~~~~~~~~~

Se debe devolver la lista discovered conteniendo tuplas (is_alive, ip) para cada direccion
IP buscada via ICMP, en donde:
* is_alive de tipo bool indica si el host con IP ip esta vivo, y 
* ip es dicha IP.
"""
def icmp_discovery(rango_red):
    discovered = []    
    rango = make_range(rango_red) 

    for dst_ip in rango:
        t = icmping(dst_ip)
        discovered.append((t[0], dst_ip))

    return discovered



""" 
Ejercicio 3
~~~~~~~~~~~

Se debe devolver la lista route conteniendo tuplas (hop_ip, d_ip) para cada hop en la ruta
al host con IP dst_ip, en donde:
* hop_ip indica la direccion IP del hop (None si esta no se pudo determinar), y
* d_ip representa la distancia (en hops) a hop_ip

Comentarios adicionales:
* Se sugiere definir un valor maximo de hops en la ruta. Puede ser 30 como sucede en Scapy.
* La variable timeout indica el tiempo (en segundos) que se esperara para recibir respuestas.
  Se puede comunicar tal valor a la primitiva sr1 de Scapy de la siguiente manera:
            sr1(packet, timeout = timeout)
* Para evitar observar la informacion mostrada por Scapy al enviar y recibir paquetes,
  se puede definir la variable verbose en 0 al invocar a sr1:
            sr1(packet, timeout = timeout, verbose = 0)
"""
def traceroute(dst_ip, timeout = 50):
    route = []
    max_hops = 30
    # Armamos paqueres de tipo IP usando ICMP
    # Vamos incrementando el time to live haciendo que en cada paso aumente el hop count
    for ttl in range(max_hops + 1):

        packet = IP(dst = dst_ip, ttl = ttl) / ICMP()

        response, unans = sr(packet, retry=1, timeout = timeout, verbose = 0)

        if response == None or len(response) == 0:
            route.append((None, ttl))	
        else:
            sent, rcvd = response[0]

            #~ si el type es echo-reply
            if (rcvd.type == 0):
                route.append((rcvd.src, sent.ttl))
            #~ si nos responde aquel al que estoy tratando de llegar termino el traceroute
                if str(rcvd.src) == dst_ip:
                    break

    # Si nos contestan Time Exceeded
            if rcvd.type == 11:
                route.append((rcvd.src, sent.ttl))       

    return route


def main():
    print "Hello"
    while True:
        cmd = input("Ingrese una instruccion: ")
        if cmd == "arping":
            dst_ip = input("IP destino: ")
            print "arping..."
            print arping(dst_ip)
        elif cmd == "icmping":
            dst_ip = input("IP destino: ")
            print "icmping..."
            print icmping(dst_ip)
        elif cmd == "arp_discovery":
            rango = input("Rango: ")
            print "discovering..."
            rangoF = arp_discovery(rango)
            for j in rangoF:
                print j	
        elif cmd == "icmp_discovery":
            rango = input("Rango: ")
            print "discovering..."
            rangoF = icmp_discovery(rango)
            for i in rangoF:
                print i
        elif cmd == "traceroute":
            dst_ip = input("IP destino: ")
            print "tracing..."
            route = traceroute(dst_ip)
            for r in route:
                print r,
        elif cmd == "exit":
            break
        else:
            print cmd
            print "las opciones validas son: arping icmping arp_discovery icmp_discovery traceroute y exit"


main()
